package com.booter.ws.controller;

import com.booter.ws.valid.FileValid;
import com.booter.ws.valid.User;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.Validator;
import java.util.Set;

/**
 *
 * ConstraintValidator 是用于自定义注解验证的接口，属于 Bean Validation（JSR 303 和 JSR 349）标准的一部分。这个接口定义了如何实施一个特定的约束注解的验证逻辑。
 * 概念
 * ConstraintValidator 接口允许开发者实现自定义的验证逻辑，这些逻辑与一个特定的约束注解相关联。开发者必须定义一个类实现这个接口，并通过泛型参数指定这个验证器关联的注解类型和要验证的数据类型。
 * 优点
 * 1. 灵活性：ConstraintValidator 允许开发者定义具体的验证逻辑，适应复杂或非标准的业务规则。你可以控制验证过程的每一个细节，从而满足特定需求。
 * 2. 集成简便： 它是 Java Bean Validation API 的一部分，能够与任何遵守该标准的技术栈（如 Spring、Hibernate）无缝集成。这意味着开发者可以在广泛的应用场景中使用相同的验证逻辑，确保数据一致性和正确性。
 * 3. 代码重用性： 一旦创建了一个 ConstraintValidator，它可以被应用于应用中的任何地方，只需通过注解即可引用。这降低了代码重复，并提高了代码的维护性。
 * 4. 改善代码可读性： 使用自定义注解和验证器可以使得验证逻辑从业务逻辑中解耦，使得代码更加清晰，逻辑更易于跟踪和维护。
 * 缺点
 * 1. 性能考虑：ConstraintValidator 的使用可能会引入性能开销，尤其是在验证逻辑复杂或数据量大时。由于验证过程常常涉及反射和动态调用，这可能影响到应用的响应时间和吞吐量。
 * 2. 复杂性增加： 设计和实现自定义的 ConstraintValidator 可能会增加开发的复杂性。开发者需要确保验证逻辑的正确性，同时，错误的实现可能导致难以发现的 bugs。
 * 3. 学习曲线： 对于新手开发者而言，理解和正确使用 Bean Validation 规范以及如何创建有效的自定义验证器可能有一定的学习曲线。
 * 4. 过度依赖注解： 在一些情况下，过度依赖注解和验证器可能会使代码变得难以理解，尤其是当验证规则非常分散而且各不相同时。这可能会导致代码的可理解性和可维护性降低。
 * 用法
 * ConstraintValidator 的实现通常需要完成以下两个主要步骤：
 * 1. 初始化方法 initialize： 这个方法在验证器的生命周期中仅被调用一次。它传递了与验证器关联的注解实例，允许验证器从注解实例中提取和存储配置详情。
 * 2. 验证方法 isValid： 这是实现验证逻辑的地方。这个方法对于每个要验证的值都会被调用，并返回一个布尔值，表示数据是否符合约束条件。
 *
 * 校验产生的异常为MethodArgumentNotValidException或者ConstraintViolationException异常
 */
@Validated
@RestController
public class ConstraintValidController {

    /**
     * 引入编程式校验工具
     */
    @Autowired
    private Validator validator;

    /**
     * SpringBoot项目中Controller的validator做参数校验不生效的问题
     * 解决：
     * springboot 2.3之前的集成在spring-boot-starter-web里了,所以不需要额外引入包
     * springboot 2.3之后需要引入 spring-boot-starter-validation
     * 单个参数校验和Bean字段校验还是有点区别的：单个参数校验需要在参数上增加校验注解，并在类上标注@Validated。
     * <p>
     * 通过自定义注解对上传文件做检测，文件大小，MIME类型，后缀名，或者扩展其他的检测方式
     * 对于上传文件的校验 需要在类上面追加@Validated，否则无法校验
     *
     * @param file
     * @return
     */

    @PostMapping("valid/file")
    public ResponseEntity<?> uploadFile(@FileValid(extensions = {"jpg", "png"}, mimeTypes = {"image/jpeg", "image/png"}, maxSize = 2 * 1024 * 1024) @RequestParam("file") MultipartFile file) {
        // 文件处理逻辑
        return ResponseEntity.ok("File uploaded successfully");
    }

    /**
     * POST、PUT请求一般会使用requestBody传递参数，这种情况下，后端使用DTO对象进行接收。只要给DTO对象加上@Validated注解就能实现自动参数校验
     *如果校验失败，会抛出MethodArgumentNotValidException异常
     *  @param u
     * @return
     */
    @PostMapping("valid/model")
    public ResponseEntity<?> model(@Valid @RequestBody User u) {
        return ResponseEntity.ok("successfully");
    }

    /**
     * requestParam/PathVariable参数校验 在这种情况下，必须在Controller类上标注@Validated注解，
     * 并在入参上声明约束注解(如@Min等)。如果校验失败，会抛出ConstraintViolationException异常
     * 与请求体验证相反，失败的验证将触发 ConstraintViolationException 异常而不是 MethodArgumentNotValidException异常。 Spring 不会为此异常注册默认异常处理器，因此在默认情况下它会导致 HTTP 状态码为 500(Internal Server Error）的响应。
     * @param id
     * @return
     */
    @GetMapping("valid/param")
    public  ResponseEntity<?> param(@RequestParam @Length(min = 2,max = 5) String id){
        return ResponseEntity.ok(id);
    }


    @PostMapping("valid/model2")
    public ResponseEntity<?> model2(@RequestBody User u) {
        Set<ConstraintViolation<User>> validate = validator.validate(u);
        if (validate.isEmpty()){
            return ResponseEntity.ok("successfully");
        }
        throw new ConstraintViolationException(validate);
//        System.out.println(validate);
//        return ResponseEntity.ok("error");
    }



}
